package com.timk.goserver.server.services;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.hibernate.Session;

import com.timk.goserver.client.board.Board;
import com.timk.goserver.client.board.BoardController;
import com.timk.goserver.client.model.ClientGameInfo;
import com.timk.goserver.client.model.ClientUserInfo;
import com.timk.goserver.client.model.GameInvite;
import com.timk.goserver.client.model.Message;
import com.timk.goserver.client.services.MessageService;
import com.timk.goserver.client.services.NoSessionException;
import com.timk.goserver.server.model.ServerGameInfo;
import com.timk.goserver.server.model.ServerUserInfo;
import com.timk.goserver.server.util.GlobalProperties;
import com.timk.goserver.server.util.HibernateUtil;
import com.timk.goserver.server.util.Mailman;

/**
 * Message service implementation
 * 
 * @author TKington
 * 
 */
public class MessageServiceImpl extends AbstractServiceServlet
								implements MessageService {
	private static final String MESSAGE_WAITING;
	
	static {
		StringBuffer text = new StringBuffer(100);
		text.append("An igoserver message is waiting for you at ");
		text.append(GlobalProperties.getProperty("base.url")).append("Main.html");
		MESSAGE_WAITING = text.toString();
	}
	
	public int sendInvite(GameInvite invite) throws NoSessionException {
		String curUser = getCurrentUsername();
		
		int size = invite.getSize();
		if(size < 5 || size > 19) {
			return STATUS_UNSUPPORTED_SIZE;
		}
		
		if(!ClientGameInfo.isValidHandicap(invite.getSize(), invite.getHandicap())) {
			return STATUS_UNSUPPORTED_HANDICAP;
		}
		
		List players = invite.getPlayerNames();
		if(invite.isRated() && (invite.getSize() != 19 || players.size() != 2)) {
			return STATUS_CANT_BE_RATED;
		}
		
		Session hbSession = HibernateUtil.startTrans();

		List usersToNotify = getOtherPlayers(hbSession, curUser, players);
		if(usersToNotify == null) {
			return STATUS_NO_SUCH_USER;
		}

		sendMessageNotification(usersToNotify);

		hbSession.save(invite);
		hbSession.getTransaction().commit();

		return STATUS_SUCCESS;
	}

	public List getInvites() throws NoSessionException {
		String curUser = getCurrentUsername();

		Session hbSession = HibernateUtil.startTrans();
		List result = hbSession.createQuery(
				"select invite from GameInvite as invite" +
				" inner join invite.playerInfo as info" +
				" where info.name = ? and info.accepted = false")
				.setString(0, curUser)
				.list();
		hbSession.getTransaction().commit();

		ArrayList invites = new ArrayList();
		for (Iterator iter = result.iterator(); iter.hasNext();) {
			GameInvite invite = (GameInvite) iter.next();
			invite.sanitize();
			invites.add(invite);
		}
		
		return invites;
	}

	public int acceptInvite(int inviteId) throws NoSessionException {
		String curUser = getCurrentUsername();

		Session hbSession = HibernateUtil.startTrans();
		try {
			GameInvite invite = (GameInvite)hbSession
					.get(GameInvite.class, new Integer(inviteId));
	
			if(invite == null) {
				return STATUS_NO_SUCH_INVITE;
			}
			
			if(!invite.setPlayerAccepted(curUser)) {
				return STATUS_WRONG_USER;
			}
			
			if(invite.allPlayersAccepted()) {
				hbSession.delete(invite);
				
				ServerGameInfo game = new ServerGameInfo(invite, hbSession);
				
				if(!curUser.equals(game.getNextToPlay())) {
					BoardController controller = new BoardController();
					Board board = controller.loadGame(game.getSize(),
							game.getSgf(), game.getDeadStones());
					
					GameServiceImpl.sendMoveNotification(hbSession, game, board);
				}
				
				hbSession.save(game);
			}
			
			return STATUS_SUCCESS;
		}
		finally {
			hbSession.getTransaction().commit();
		}
	}

	public int refuseInvite(int inviteId) throws NoSessionException {
		String curUser = getCurrentUsername();

		Session hbSession = HibernateUtil.startTrans();
		GameInvite invite = (GameInvite)hbSession
				.get(GameInvite.class, new Integer(inviteId));
		hbSession.getTransaction().commit();

		if(invite == null) {
			return STATUS_NO_SUCH_INVITE;
		}
		
		if(!invite.findPlayer(curUser)) {
			return STATUS_WRONG_USER;
		}
		
		hbSession = HibernateUtil.startTrans();
		hbSession.delete(invite);
		
		List players = invite.getPlayerNames();
		StringBuffer text = new StringBuffer(100);
		text.append("Players:\n");
		for (Iterator iter = players.iterator(); iter.hasNext();) {
			String player = (String) iter.next();
			text.append(player).append('\n');
		}
		text.append('\n').append(curUser).append(" has refused this game invitation.");
		
		List otherPlayers = getOtherPlayers(hbSession, curUser, players);
		sendMessage(hbSession, curUser, otherPlayers, "Invite Refused", text.toString());
		
		hbSession.getTransaction().commit();

		return STATUS_SUCCESS;
	}
	
	public int sendMessage(Message message) throws NoSessionException {
		String curUser = getCurrentUsername();

		if (!message.getFromUser().equals(curUser)) {
			return STATUS_WRONG_USER;
		}
		
		if(message.getToUser().equals("ALL")) {
			return sendToAll(message);
		}

		Session hbSession = HibernateUtil.startTrans();

		ServerUserInfo toUser = ServerUserInfo.findUser(message.getToUser(), hbSession);
		if(toUser == null) {
			return STATUS_NO_SUCH_USER;
		}
		
		ArrayList toUsers = new ArrayList();
		toUsers.add(toUser);
		sendMessage(hbSession, message.getFromUser(), toUsers,
				message.getSubject(), message.getText());
	
		hbSession.getTransaction().commit();

		return STATUS_SUCCESS;
	}
	
	/**
	 * Returns a List&lt;ServerUserInfo&gt; containing all except the current player
	 * @param hbSession the active session
	 * @param curUser the current user
	 * @param players List&lt;String&gt; of player names
	 * @return List&lt;ServerUserInfo&gt;
	 */
	public static List getOtherPlayers(Session hbSession,
			String curUser, List players) {
		//	TODO: move getOtherPlayers?
		ArrayList otherPlayers = new ArrayList();
		for (Iterator iter = players.iterator(); iter.hasNext();) {
			String player = (String) iter.next();
			
			if(!player.equals(curUser)) {
				ServerUserInfo user = ServerUserInfo.findUser(player, hbSession);
				
				if(user == null) {
					return null;
				}
				
				otherPlayers.add(user);
			}
		}
		
		return otherPlayers;
	}
	
	private int sendToAll(Message message) {
		Session hbSession = HibernateUtil.startTrans();
		
		List users = hbSession
				.createQuery("from ServerUserInfo order by username")
				.list();
		sendMessage(hbSession, message.getFromUser(), users,
				message.getSubject(), message.getText());
		
		hbSession.getTransaction().commit();
		
		return STATUS_SUCCESS;
	}
	
	/**
	 * Send messages to a list of users
	 * @param hbSession the active session
	 * @param from the user sending the message
	 * @param users List&lt;ServerUserInfo&gt; of user to send to
	 * @param subject
	 * @param body
	 */
	public static void sendMessage(Session hbSession, String from,
			List users, String subject, String body) {
		for (Iterator iter = users.iterator(); iter.hasNext();) {
			ServerUserInfo user = (ServerUserInfo) iter.next();
			
			Message msg = new Message(from, user.getUsername(), subject, body);
			hbSession.save(msg);	
		}
		
		sendMessageNotification(users);
	}
	
	/**
	 * Sends email notification that a message has been received
	 * @param users List&lt;ServerUserInfo&gt;
	 */
	private static void sendMessageNotification(List users) {
		ArrayList usersToNotify = new ArrayList();
		for (Iterator iter = users.iterator(); iter.hasNext();) {
			ServerUserInfo user = (ServerUserInfo) iter.next();
			
			if(user.getNotification() != ClientUserInfo.NOTIFICATION_NONE) {
				usersToNotify.add(user.getEmail());
			}
		}
		
		Mailman.sendMessage(usersToNotify, "Igoserver Notification",
				MESSAGE_WAITING, false);
	}

	public List getMessages() throws NoSessionException {
		String curUser = getCurrentUsername();

		Session hbSession = HibernateUtil.startTrans();
		List result = hbSession.createQuery("from Message as i where i.toUser = ?")
				.setString(0, curUser)
				.list();
		hbSession.getTransaction().commit();

		for (Iterator iter = result.iterator(); iter.hasNext();) {
			Message msg = (Message) iter.next();
			msg.sanitize();
		}
		
		return new ArrayList(result);
	}

	public int deleteMessage(int messageId) throws NoSessionException {
		String curUser = getCurrentUsername();

		Session hbSession = HibernateUtil.startTrans();
		Message message = (Message)hbSession
				.get(Message.class, new Integer(messageId));
		hbSession.getTransaction().commit();

		if(message == null) {
			return STATUS_NO_SUCH_MESSAGE;
		}
		
		if(!curUser.equals(message.getToUser())) {
			return STATUS_WRONG_USER;
		}
		
		hbSession = HibernateUtil.startTrans();
		hbSession.delete(message);
		hbSession.getTransaction().commit();

		return STATUS_SUCCESS;
	}
}
